package ru.amse.tsyganov.jumleditor.view;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import ru.amse.tsyganov.jumleditor.commands.Command;
import ru.amse.tsyganov.jumleditor.commands.CommandFactory;
import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.properties.StringProperty;

public abstract class StateView<T extends State> extends ViewElement<T> {
	
	private static final String STATE_VIEW_X = "stateViewX";
	
	private static final String STATE_VIEW_Y = "stateViewY";
	
	private static final String STATE_VIEW_WIDTH = "stateViewWidth";
	
	private static final String STATE_VIEW_HEIGHT = "stateViewHeight";
	
	private final ActivityGraphView<? extends ActivityGraph> activityGraphView;
	
	private int stateViewX;
	
	private int stateViewY;
	
	private int stateViewWidth = 70;
	
	private int stateViewHeight = 30;
	
	private final List<ActivePoint> points;
		
	private final StringProperty mainProperty = new StringProperty("Name") {

		@Override
		public String getPropertyValue() {
			return getModel().getName();
		}

		@Override
		public void setPropertyValue(String newValue) {
			getModel().setName(newValue);
		}
	
	};
		
	public StateView(T model,
			ActivityGraphView<? extends ActivityGraph> activityGraphView) {
		
		super(model);
		points = createActivePoints();
		this.activityGraphView = activityGraphView;
		initProperties();
	}
	
	public void readFromXml(Node element) {
		stateViewX = Integer.parseInt(element.getAttributes().
				getNamedItem(STATE_VIEW_X).getNodeValue());
		stateViewY = Integer.parseInt(element.getAttributes().
				getNamedItem(STATE_VIEW_Y).getNodeValue());
		stateViewWidth = Integer.parseInt(element.getAttributes().
				getNamedItem(STATE_VIEW_WIDTH).getNodeValue());
		stateViewHeight = Integer.parseInt(element.getAttributes().
				getNamedItem(STATE_VIEW_HEIGHT).getNodeValue());
	}

	public void writeToXml(Element element) {
		element.setAttribute(STATE_VIEW_X, Integer.toString(stateViewX));
		element.setAttribute(STATE_VIEW_Y, Integer.toString(stateViewY));
		element.setAttribute(STATE_VIEW_WIDTH, Integer.toString(stateViewWidth));
		element.setAttribute(STATE_VIEW_HEIGHT, Integer.toString(stateViewHeight));
	}
	
	public int getX() {
		return stateViewX;
	}
	
	public void setX(int x) {
		this.stateViewX = x;
	}
	
	public int getY() {
		return stateViewY;
	}
	
	public void setY(int y) {
		this.stateViewY = y;
	}
	
	public int getWidth() {
		return stateViewWidth;
	}
	
	public void setWidth(int newWidth) {
		if (newWidth < 0) {
			throw new IllegalArgumentException();
		}
		
		this.stateViewWidth = newWidth;
	}
	
	public int getHeight() {
		return stateViewHeight;
	}
	
	public void setHeight(int newHeight) {
		if (newHeight < 0) {
			throw new IllegalArgumentException();
		}
		
		this.stateViewHeight = newHeight;
	}
	
	@Override
	public Point getCenterPoint() {
		return new Point(getX() + getWidth()/2, getY() + getHeight()/2);
	}
	
	public Rectangle getRectangle() {
		return new Rectangle(stateViewX, stateViewY, stateViewWidth, stateViewHeight);
	}
	
	@Override
	public void accept(Visitor v) {
		v.visitStateView(this);
		for (Transition t : getModel().getStateVertex().getOutgoing()) {
			TransitionView<? extends Transition> transitionView = 
				getModelViewMap().getTransitionsMap().get(t);
			transitionView.accept(v);
		}
	}
	
	@Override
	public final void paint(GC gc) {
		assert gc != null;
		
		paintFigure(gc);
		
		if (getSelectedItems().selectedStatesContains(this)) {
			
			gc.setForeground(Colors.getBlackColor());
			gc.setLineStyle(SWT.LINE_DOT);
			
			final int MARGIN = 2;
			
			gc.drawRectangle(getX() - MARGIN,
					getY() - MARGIN,
					getWidth() + 2 * MARGIN,
					getHeight() + 2 * MARGIN);
			
			gc.setLineStyle(SWT.LINE_SOLID);
		}		
	}
	
	public void paintTransitions(GC gc) {
		for (Transition t : getModel().getStateVertex().getOutgoing()) {
			TransitionView<? extends Transition> transitionView = 
				getModelViewMap().getTransitionsMap().get(t);
			transitionView.paint(gc);
		}
	}
	
	public abstract void prepaintEvent(GC gc);
	
	@Override
	public List<? extends ActivePoint> getActivePoints() {
		if (getSelectedItems().selectedStatesContains(this)) {
			return points;
		} else {
			return Collections.emptyList();
		}
	}

	@Override
	public ActivityGraphView<? extends ActivityGraph> getActivityGraphView() {
		return activityGraphView;
	}
	
	public abstract void paintFigure(GC gc);
	
	@Override
	public void paintActivePoints(GC gc) {
		if (getSelectedItems().selectedStatesContains(this)) {
			for (ActivePoint p : getActivePoints()) {
				p.paintActivePoint(gc);
			}
		}
	}
	
	public Point getConnectionPoint(Point sourcePoint, boolean in) {
		return TransitionDraughtsman.getConnectionPoint(sourcePoint, getRectangle());
	}
	
	private void initProperties() {
		super.getProperties().addProperty(mainProperty);
	}
	
	@Override
	public StringProperty getMainProperty() {
		return mainProperty;
	}
	
	private List<ActivePoint> createActivePoints() {
		ResizeActivePoint topLeftPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNW();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						getTemporaryActivePointX(), 
						getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint topRightPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNE();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}	
		};
		
		ResizeActivePoint bottomLeftPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNE();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(getTemporaryActivePointX(), stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint bottomRightPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeNW();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint topCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeN();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight + stateViewY - getTemporaryActivePointY();
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth / 2;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, 
						getTemporaryActivePointY(), 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint rightCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeW();
			}
			@Override
			public int getNewWidth() {
				return getTemporaryActivePointX() - stateViewX;
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight / 2;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(
						stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}	
		};
		
		ResizeActivePoint bottomCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						stateViewX, 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeN();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth;
			}
			@Override
			public int getNewHeight() {
				return getTemporaryActivePointY() - stateViewY;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX + stateViewWidth / 2;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(stateViewX, stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		ResizeActivePoint leftCenterPoint = new ResizeActivePoint() {
			@Override
			public Command createCommandForAction() {
				if (!isNewResultsCorrect()) {
					return null;
				}
				if (stateViewHeight == getNewHeight() && 
						stateViewWidth == getNewWidth()) {
					
					return null;
				}
				return CommandFactory.createResizeStateCommand(
						StateView.this, 
						getTemporaryActivePointX(), 
						stateViewY, 
						getNewWidth(), 
						getNewHeight());
			}
			@Override
			public Cursor getCursor() {
				return Cursors.getCursorSizeE();
			}
			@Override
			public int getNewWidth() {
				return stateViewWidth + stateViewX - getTemporaryActivePointX();
			}
			@Override
			public int getNewHeight() {
				return stateViewHeight;
			}
			@Override
			public int getCurrentActivePointX() {
				return stateViewX;
			}
			@Override
			public int getCurrentActivePointY() {
				return stateViewY + stateViewHeight / 2;
			}
			@Override
			public Rectangle getTemporaryRectangle() {
				return new Rectangle(getTemporaryActivePointX(), stateViewY, 
						getNewWidth(), getNewHeight());
			}
		};
		
		List<ActivePoint> points = new ArrayList<ActivePoint>(8);
		points.add(topLeftPoint);
		points.add(topCenterPoint);
		points.add(topRightPoint);
		points.add(rightCenterPoint);
		points.add(bottomRightPoint);
		points.add(bottomCenterPoint);
		points.add(bottomLeftPoint);
		points.add(leftCenterPoint);
		return points;
	}

	protected List<ActivePoint> createNominalActivePoints() {
		NominalActivePoint topLeftPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY();
			}
		};
		
		NominalActivePoint topRightPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX() + getWidth();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY();
			}
		};
		
		NominalActivePoint bottomLeftPoint = new NominalActivePoint() {

			@Override
			public int getCurrentActivePointX() {
				return getX();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY() + getHeight();
			}
		};
		
		NominalActivePoint bottomRightPoint = new NominalActivePoint() {
			
			@Override
			public int getCurrentActivePointX() {
				return getX() + getWidth();
			}
			
			@Override
			public int getCurrentActivePointY() {
				return getY() + getHeight();
			}
		};
		
		List<ActivePoint> points = new ArrayList<ActivePoint>(8);
		points.add(topLeftPoint);
		points.add(topRightPoint);
		points.add(bottomRightPoint);
		points.add(bottomLeftPoint);
		return points;
	}
}
